/*
 *  Copyright (c) 2022 ZhuHai Jieli Technology Co.,Ltd.
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/*********************************************************************************************
    *   Filename        : lp_timer_hw.c

    *   Description     :

    *   Author          : Bingquan

    *   Email           : bingquan_cai@zh-jieli.com

    *   Last modifiled  : 2018-11-01 17:37

    *   Copyright:(c)JIELI  2011-2017  @ , All Rights Reserved.
*********************************************************************************************/
#include "lp_timer_hw.h"
#include "asm/cpu.h"
#include "power_hw.h"
#include "asm/hwi.h"
#include "gpio.h"
#include "asm/power_interface.h"

#define LOG_TAG_CONST       LP_TIMER
#define LOG_TAG             "[LP-TIMER]"
#define LOG_ERROR_ENABLE
#define LOG_DEBUG_ENABLE
#define LOG_INFO_ENABLE
#include "debug.h"

#define IRQ_FUNCTION_PROTOTYPE(idx, hdl) \
    __attribute__((interrupt("")))

#define IRQ_REGISTER(idx, hdl, prio) \
    request_irq(idx, prio, hdl, 0)
/**
 * @brief Low Power Debug Signal(mapping to DSview)
 */

#define DEBUG_SINGAL_IRQ_LP0(x)         // if (x) IO_DEBUG_1(A, 6) else IO_DEBUG_0(A, 6)
#define DEBUG_SINGAL_IRQ_LP1(x)         // if (x) IO_DEBUG_1(B, 3) else IO_DEBUG_0(B, 3)

/* ----------------------------------------------------------- */
#define __this (&low_power_hdl)

static void lp_timer0_irq_handler(void);

static void lp_timer1_irq_handler(void);

static u32 hw_osc_hz = 0;
static u8 hw_osc_type = 0;
static u32 vir_rtc_interval_time = 0;

extern u32 __get_lrc_hz(void);

u32 __tus_cnt_for_virtual_rtc(u32 x)
{
    u64 y;
    y = ((u64)x) * 1000000L;
    y = y / (__get_lrc_hz());

    return (u32)y;
}

/*
 *  Low power timer related functions
 */
void __hw_nv_timer0_dump(void)
{
    u16 addr;

    log_info("P3_LP_RSC00 : 0x%x", p33_rx_1byte(P3_LP_RSC00));
    log_info("P3_LP_RSC01 : 0x%x", p33_rx_1byte(P3_LP_RSC01));
    log_info("P3_LP_RSC02 : 0x%x", p33_rx_1byte(P3_LP_RSC02));
    log_info("P3_LP_RSC03 : 0x%x", p33_rx_1byte(P3_LP_RSC03));

    log_info("P3_LP_PRD00 : 0x%x", p33_rx_1byte(P3_LP_PRD00));
    log_info("P3_LP_PRD01 : 0x%x", p33_rx_1byte(P3_LP_PRD01));
    log_info("P3_LP_PRD02 : 0x%x", p33_rx_1byte(P3_LP_PRD02));
    log_info("P3_LP_PRD03 : 0x%x", p33_rx_1byte(P3_LP_PRD03));

    log_info("P3_LP_TMR0_CON : 0x%x", p33_rx_1byte(P3_LP_TMR0_CON));

    log_info("P3_PMU_CON1 : 0x%x", p33_rx_1byte(P3_PMU_CON1));
    log_info("P3_PMU_CON4 : 0x%x", p33_rx_1byte(P3_PMU_CON4));
}

bool __hw_nv_timer_set_time(u8 index, u32 usec)
{
    u32 max_cnt, ticks;
    u16 addr;
    u32 tmp32;
    u32 sleep;
    u8 inv_temp;

    ticks = __tcnt_us_no_carry(usec);

    if ((index != P3_LP_RSC10) && (ticks < __this->reserve_time)) {
        return FALSE;
    }

    addr = index;
    p33_cs_h(addr);

    p33_buf((u8)((addr & 0x300) >> 8));
    p33_buf((u8)(addr & 0xff));

    // RSC
    tmp32 = ticks - __this->Tcke  -
            (1 << __this->hw.kstb4) -
            (1 << __this->hw.kstb5) -
            (1 << __this->hw.kstb6);
    sleep = tmp32;

    inv_temp = ((u8)(tmp32 >> 24));
    inv_temp ^= 0x80;

    p33_buf(inv_temp);
    p33_buf((u8)(tmp32 >> 16));
    p33_buf((u8)(tmp32 >> 8));
    p33_buf((u8)(tmp32 >> 0));

    // PRD
    tmp32 = ticks;
    p33_buf((u8)(tmp32 >> 24));
    p33_buf((u8)(tmp32 >> 16));
    p33_buf((u8)(tmp32 >> 8));
    p33_buf((u8)(tmp32 >> 0));
    p33_cs_l;

    inv_temp ^= 0x80;
    p33_tx_1byte(index, inv_temp);

    return TRUE;
}

u32 __hw_nv_timer_get_stc(u8 index, u8 virtual_rtc_flag)
{
    u32 tick;
    u16 addr;

    addr = P3_LP_CNTRD0;
    p33_cs_h(addr);
    p33_buf((P33_OR << 5) | (u8)((addr & 0x300) >> 8));               // or    //adr 3
    p33_buf((u8)(addr & 0xff));       // wr    //adr 3
    p33_buf(index);
    p33_buf(0);
    tick = p33_buf(0) << 24;
    tick |= p33_buf(0) << 16;
    tick |= p33_buf(0) << 8;
    tick |= p33_buf(0);
    p33_cs_l;

    if (virtual_rtc_flag) {
        return __tus_cnt_for_virtual_rtc(tick);
    } else {
        return __tus_cnt(tick);
    }
}

u32 __hw_nv_timer_get_time(u8 index, u8 index2)
{
    u32 cnt, prd;
    u16 addr;

    addr = P3_LP_CNTRD0;
    p33_cs_h(addr);
    p33_buf((P33_OR << 5) | (u8)((addr & 0x300) >> 8));               // or    //adr 3
    p33_buf((u8)(addr & 0xff));       // wr    //adr 3
    p33_buf(index);
    p33_buf(0);
    cnt = p33_buf(0) << 24;
    cnt |= p33_buf(0) << 16;
    cnt |= p33_buf(0) << 8;
    cnt |= p33_buf(0);
    p33_cs_l;

    addr = index2;
    p33_cs_h(addr);
    p33_buf((BIT(7)) | (u8)((addr & 0x300) >> 8));               // or    //adr 3
    p33_buf((u8)(addr & 0xff));       // wr    //adr 3
    prd = p33_buf(0) << 24;
    prd |= p33_buf(0) << 16;
    prd |= p33_buf(0) << 8;
    prd |= p33_buf(0);
    p33_cs_l;

    return (prd - cnt);
}

u32 __hw_nv_timer_get_pass_time(u8 index, u8 virtual_rtc_flag)
{
    u32 cnt;
    u16 addr;

    addr = P3_LP_CNTRD0;
    p33_cs_h(addr);
    p33_buf((P33_OR << 5) | (u8)((addr & 0x300) >> 8));               // or    //adr 3
    p33_buf((u8)(addr & 0xff));       // wr    //adr 3
    p33_buf(index);
    p33_buf(0);
    cnt = p33_buf(0) << 24;
    cnt |= p33_buf(0) << 16;
    cnt |= p33_buf(0) << 8;
    cnt |= p33_buf(0);
    p33_cs_l;

    if (virtual_rtc_flag) {
        return __tus_cnt_for_virtual_rtc(cnt);
    } else {
        return __tus_cnt(cnt);
    }
}
u32 __hw_nv_timer_get_period(u8 index, u8 virtual_rtc_flag)
{
    u32 prd;
    u16 addr;

    addr = index;
    p33_cs_h(addr);
    p33_buf((BIT(7)) | (u8)((addr & 0x300) >> 8));               // or    //adr 3
    p33_buf((u8)(addr & 0xff));       // wr    //adr 3
    prd = p33_buf(0) << 24;
    prd |= p33_buf(0) << 16;
    prd |= p33_buf(0) << 8;
    prd |= p33_buf(0);
    p33_cs_l;

    if (virtual_rtc_flag) {
        return __tus_cnt_for_virtual_rtc(prd);
    } else {
        return __tus_cnt(prd);
    }
}

static bool __hw_nv_timer_reset_time(u8 index, u32 usec)
{
    u32 ticks;
    u16 addr;
    u32 tmp32;
    u8 inv_temp;

    ticks = __tcnt_us(usec);

    addr = index;
    p33_cs_h(addr);

    p33_buf((u8)((addr & 0x300) >> 8));
    p33_buf((u8)(addr & 0xff));

    tmp32 = ticks - __this->Tcke;

    inv_temp = ((u8)(tmp32 >> 24));
    inv_temp ^= 0x80;

    p33_buf(inv_temp);
    p33_buf((u8)(tmp32 >> 16));
    p33_buf((u8)(tmp32 >> 8));
    p33_buf((u8)(tmp32 >> 0));

    // PRD
    tmp32 = ticks;
    inv_temp = ((u8)(tmp32 >> 24));
    inv_temp ^= 0x80;

    p33_buf(inv_temp);
    p33_buf((u8)(tmp32 >> 16));
    p33_buf((u8)(tmp32 >> 8));
    p33_buf((u8)(tmp32 >> 0));
    p33_cs_l;

    p33_xor_1byte(index, 0x80);
    p33_xor_1byte(index + 4, 0x80);

    return TRUE;
}

static bool hw_nv_timer_reset_time(u8 index, u32 usec)
{
    u32 ticks;
    u16 addr;
    u32 tmp32;
    u8 inv_temp;

    ticks = __tcnt_us_lrc(usec);

    addr = index;
    p33_cs_h(addr);

    p33_buf((u8)((addr & 0x300) >> 8));
    p33_buf((u8)(addr & 0xff));

    tmp32 = ticks - __this->Tcke;

    inv_temp = ((u8)(tmp32 >> 24));
    inv_temp ^= 0x80;

    p33_buf(inv_temp);
    p33_buf((u8)(tmp32 >> 16));
    p33_buf((u8)(tmp32 >> 8));
    p33_buf((u8)(tmp32 >> 0));

    // PRD
    tmp32 = ticks;
    inv_temp = ((u8)(tmp32 >> 24));
    inv_temp ^= 0x80;

    p33_buf(inv_temp);
    p33_buf((u8)(tmp32 >> 16));
    p33_buf((u8)(tmp32 >> 8));
    p33_buf((u8)(tmp32 >> 0));
    p33_cs_l;

    p33_xor_1byte(index, 0x80);
    p33_xor_1byte(index + 4, 0x80);

    return TRUE;
}

void __attribute__((weak))set_virtual_rtc_tick(u64 sec)
{
}

IRQ_FUNCTION_PROTOTYPE(IRQ_LP_TIMER1_IDX, rtc_lp_timer1_irq_handler) static void rtc_lp_timer1_irq_handler(void)
{
    u8 reg;
    u64 actual_time = 0;
    u32 lrc_hz = 0;

    lrc_hz = __get_lrc_hz();


    if (hw_osc_type == OSC_TYPE_LRC) {
        actual_time = (u64)((u64)vir_rtc_interval_time * 1000000 * (u64)hw_osc_hz / (u64)lrc_hz); // ns
    } else {
        actual_time = vir_rtc_interval_time * 1000000;
    }
    reg = p33_rx_1byte(P3_LP_TMR1_CON);

    if (reg & BIT(7)) {
        p33_or_1byte(P3_LP_TMR1_CON, BIT(6));
        set_virtual_rtc_tick(actual_time);
    }

    if (reg & BIT(5)) {
        p33_or_1byte(P3_LP_TMR1_CON, BIT(4));
    }
}

bool rtc_hw_nv_timer1_set_time(u32 usec)
{
    bool ret;

    ret = __hw_nv_timer_set_time(P3_LP_RSC10, usec);

    p33_or_1byte(P3_LP_TMR1_CON, BIT(0));
    p33_tx_1byte(P3_PMU_CON4, PD_CON4_INIT_TMR1_RTC);

    LP_KST ;

    return ret;
}

void hw_nv_timer1_reset_time(u32 usec)
{
    u32 ticks;
    static u32 last_ticks;

    ticks = __tcnt_us_lrc(usec);
    if ((last_ticks != ticks) && ((NV_TIMER1_GET_PASS_TIME() + __this->recover_total_time) < usec)) {
        __hw_nv_timer1_reset_time(usec);
        hw_osc_hz = __get_lrc_hz();
        vir_rtc_interval_time = usec / 1000;
        last_ticks = ticks;
    }
}

void rtc_hw_nv_timer1_init(u8 osc_type, u32 osc_hz, u16 interval)
{
    u32 interval_time = interval * 1000;
    u16 addr;

    INIT_LIST_HEAD(&__this->group1.request_head);

    // Clear LP timer pending
    p33_tx_1byte(P3_LP_TMR1_CON, (BIT(4) | BIT(6)));
    p33_tx_1byte(P3_LP_TMR1_CON, pd_tmr1_rtc_init);

    IRQ_REGISTER(IRQ_LP_TIMER1_IDX, rtc_lp_timer1_irq_handler, 3);

    SFR(JL_P33->PMU_CON, 3, 1, 1);

    hw_osc_type = osc_type;
    hw_osc_hz = osc_hz;
    vir_rtc_interval_time = interval;

    rtc_hw_nv_timer1_set_time(interval_time);
}

u32 get_nv_timer1_pass_time()
{
    return __hw_nv_timer_get_pass_time(PD_LP1_CNT_READ, 1);
}

u32 get_nv_timer1_remain_time()
{
    return NV_TIMER1_GET_TIME();
}

static u32 __power_get_timeout(void *priv)
{
    u32 ticks = 0;
    u8 reg = p33_rx_1byte(P3_LP_TMR1_CON);

    if (reg & BIT(5)) {
        return 0;
    }

    if (reg & BIT(7)) {
        return 0;
    }

    u32 nv_timer1_remain_cnt = NV_TIMER1_GET_TIME();
    if (nv_timer1_remain_cnt >= __this->reserve_time) {
        ticks = nv_timer1_remain_cnt - __this->reserve_time;
    } else {
        ticks = nv_timer1_remain_cnt;
    }

    return __tus_cnt_for_virtual_rtc(ticks);
}

const struct low_power_operation nv_timer1_power_ops  = {
    .name           = "nv_timer1_power_ops",
    .get_timeout     = __power_get_timeout,

    .suspend_probe     = NULL,
    .suspend_post     = NULL,
    .resume         = NULL,
};

void *nv_timer1_low_power;
void rtc_nv_timer1_sleep_init(void)
{
    nv_timer1_low_power = low_power_sys_get(NULL, &nv_timer1_power_ops);
}

bool __hw_nv_timer0_set_time(u32 usec)
{
    bool ret;

    ret = __hw_nv_timer_set_time(P3_LP_RSC00, usec);

    p33_tx_1byte(P3_PMU_CON4, PD_CON4_INIT_TMR0);

    return ret;
}

void __hw_nv_timer0_enable(void)
{
    p33_or_1byte(P3_LP_TMR0_CON, BIT(0));
}

void __hw_nv_timer0_disable(void)
{
    p33_and_1byte(P3_LP_TMR0_CON, ~BIT(0));
    p33_or_1byte(P3_LP_TMR0_CON, BIT(6));
    p33_or_1byte(P3_LP_TMR0_CON, BIT(4));
}

void __hw_nv_timer0_hw_kick_start(void)
{
    __hw_nv_timer0_enable();

    p33_tx_1byte(P3_PMU_CON4, PD_CON4_INIT_TMR0);
}

void __hw_nv_timer0_sw_kick_start(void)
{
    __hw_nv_timer0_enable();

    p33_tx_1byte(P3_PMU_CON4, PD_CON4_INIT_TMR1);

    LP_KST;
}

void __hw_nv_timer0_init(void)
{
    u16 addr;

    INIT_LIST_HEAD(&__this->group0.request_head);

    // Clear LP timer pending
    p33_tx_1byte(P3_LP_TMR0_CON, (BIT(4) | BIT(6)));
    p33_tx_1byte(P3_LP_TMR0_CON, pd_tmr0_init);

    SFR(JL_P33->PMU_CON, 2, 1, 1);
}

bool __hw_nv_timer0_reset_time(u32 usec)
{
    bool ret;

    ret = __hw_nv_timer_reset_time(P3_LP_RSC00, usec);

    p33_tx_1byte(P3_PMU_CON4, PD_CON4_INIT_TMR0);

    return ret;
}
/*
 *  Low power timer1 related functions
 */
static void __hw_nv_timer1_dump(void)
{
    u16 addr;

    log_info("P3_LP_RSC10 : 0x%x", p33_rx_1byte(P3_LP_RSC10));
    log_info("P3_LP_RSC11 : 0x%x", p33_rx_1byte(P3_LP_RSC11));
    log_info("P3_LP_RSC12 : 0x%x", p33_rx_1byte(P3_LP_RSC12));
    log_info("P3_LP_RSC13 : 0x%x", p33_rx_1byte(P3_LP_RSC13));

    log_info("P3_LP_PRD10 : 0x%x", p33_rx_1byte(P3_LP_PRD10));
    log_info("P3_LP_PRD11 : 0x%x", p33_rx_1byte(P3_LP_PRD11));
    log_info("P3_LP_PRD12 : 0x%x", p33_rx_1byte(P3_LP_PRD12));
    log_info("P3_LP_PRD13 : 0x%x", p33_rx_1byte(P3_LP_PRD13));

    log_info("P3_LP_TMR1_CON : 0x%x", p33_rx_1byte(P3_LP_TMR1_CON));

    log_info("P3_PMU_CON1 : 0x%x", p33_rx_1byte(P3_PMU_CON1));
    log_info("P3_PMU_CON4 : 0x%x", p33_rx_1byte(P3_PMU_CON4));
}

void __hw_nv_timer1_enable(void)
{
    p33_or_1byte(P3_LP_TMR1_CON, BIT(0));
}

void __hw_nv_timer1_disable(void)
{
    p33_and_1byte(P3_LP_TMR1_CON, ~BIT(0));
    p33_or_1byte(P3_LP_TMR1_CON, BIT(6));
    p33_or_1byte(P3_LP_TMR1_CON, BIT(4));
}

bool __hw_nv_timer1_set_time(u32 usec)
{
    bool ret;

    ret = __hw_nv_timer_set_time(P3_LP_RSC10, usec);

    __hw_nv_timer1_enable();

    if (usec != -2) {
        p33_tx_1byte(P3_PMU_CON4, PD_CON4_INIT_TMR1);
    }

    LP_KST;

    return ret;
}

void __hw_nv_timer1_init(void)
{
    u16 addr;

    INIT_LIST_HEAD(&__this->group1.request_head);

    p33_tx_1byte(P3_LP_TMR1_CON, (BIT(4) | BIT(6)));
    p33_tx_1byte(P3_LP_TMR1_CON, pd_tmr1_init);

    SFR(JL_P33->PMU_CON, 3, 1, 1);
}

bool __hw_nv_timer1_reset_time(u32 usec)
{
    bool ret;

    ret = hw_nv_timer_reset_time(P3_LP_RSC10, usec);

    p33_tx_1byte(P3_PMU_CON4, PD_CON4_INIT_TMR1_RTC);

    return ret;
}

bool __hw_nv_timer_is_runnig(u8 index)
{
    index = (index) ? 2 : 1;

    return (p33_rx_1byte(P3_PMU_CON5) & BIT(index)) ? TRUE : FALSE;
}

IRQ_FUNCTION_PROTOTYPE(IRQ_LP_TIMER0_IDX, lp_timer0_irq_handler) static void lp_timer0_irq_handler(void)
{
    u8 reg;

    DEBUG_SINGAL_IRQ_LP0(1);
    reg = p33_rx_1byte(P3_LP_TMR0_CON);

    if (reg & BIT(7)) {
        p33_or_1byte(P3_LP_TMR0_CON, BIT(6));
    }

    if (reg & BIT(5)) {
        p33_or_1byte(P3_LP_TMR0_CON, BIT(4));
        u8 sys_vdd;
        sys_vdd = GET_SYSVDD_VOL_SEL();
        SYSVDD_VOL_SEL(SYSVDD_VOL_SEL_120V);
        __low_power_resume(__this->group0.timeout);
        SYSVDD_VOL_SEL(sys_vdd);
    }
    DEBUG_SINGAL_IRQ_LP0(0);
}

IRQ_FUNCTION_PROTOTYPE(IRQ_LP_TIMER1_IDX, lp_timer1_irq_handler) static void lp_timer1_irq_handler(void)
{
    u8 reg;

    DEBUG_SINGAL_IRQ_LP1(1);
    reg = p33_rx_1byte(P3_LP_TMR1_CON);

    if (reg & BIT(7)) {
        p33_or_1byte(P3_LP_TMR1_CON, BIT(6));
    }

    if (reg & BIT(5)) {
        p33_or_1byte(P3_LP_TMR1_CON, BIT(4));
    }
    DEBUG_SINGAL_IRQ_LP1(0);
}
